package com.example.chat.handler;

import com.example.chat.domain.MessageDTO;
import com.example.chat.service.GroupService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * WebSocket 聊天处理器
 * 
 * 该类负责处理 WebSocket 连接的整个生命周期，包括连接建立、消息处理和连接关闭。
 * 它作为聊天模块的核心组件，实现了群组聊天的主要业务逻辑。
 */
@Component
public class ChatWebSocketHandler extends TextWebSocketHandler {
    private static final Logger logger = LoggerFactory.getLogger(ChatWebSocketHandler.class);
    
    /**
     * 保存所有活跃的 WebSocket 连接
     * key: 用户ID，value: 对应的 WebSocket 会话
     * 使用 ConcurrentHashMap 确保线程安全
     */
    private static final Map<Long, WebSocketSession> userSessions = new ConcurrentHashMap<>();
    
    /**
     * 保存群组聊天会话映射关系
     * 外层 key: 群组ID
     * 内层 Map: 该群组内所有用户的会话，key 为用户ID，value 为对应的 WebSocket 会话
     * 使用 ConcurrentHashMap 确保线程安全
     */
    private static final Map<Long, Map<Long, WebSocketSession>> groupSessions = new ConcurrentHashMap<>();
    
    /**
     * JSON 对象映射器，用于序列化和反序列化消息
     */
    @Autowired
    private ObjectMapper objectMapper;
    
    /**
     * 群组服务，用于保存消息和管理群组相关操作
     */
    @Autowired
    private GroupService groupService;
    
    /**
     * 处理 WebSocket 连接建立事件
     * 
     * 当客户端成功建立 WebSocket 连接时调用此方法。
     * 主要职责：
     * 1. 保存用户会话到会话映射表
     * 2. 如果是群聊，将用户添加到对应群组的会话映射中
     * 3. 向群组广播用户加入的消息
     * 
     * @param session 新建立的 WebSocket 会话
     * @throws Exception 如果处理过程中发生错误
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // 从session中获取用户ID和群组ID
        Long userId = (Long) session.getAttributes().get("userId");
        Long groupId = (Long) session.getAttributes().get("groupId");
        
        if (userId != null) {
            // 保存用户会话
            userSessions.put(userId, session);
            
            // 如果是加入群聊
            if (groupId != null) {
                // 添加到群组会话
                groupSessions.computeIfAbsent(groupId, k -> new ConcurrentHashMap<>())
                             .put(userId, session);
                
                // 向群组广播用户加入消息
                String username = getUsernameFromUserId(userId);
                MessageDTO joinMessage = new MessageDTO(
                    userId, username, groupId, username + " 加入了群聊"
                );
                broadcastToGroup(groupId, joinMessage, userId);
                
                logger.info("用户 {} 加入群聊 {}", userId, groupId);
            }
        }
    }
    
    /**
     * 处理接收到的文本消息
     * 
     * 当接收到客户端发送的 WebSocket 消息时调用此方法。
     * 主要职责：
     * 1. 反序列化接收到的 JSON 消息为 MessageDTO 对象
     * 2. 设置消息发送时间和发送者信息
     * 3. 保存消息到数据库
     * 4. 广播消息给群组内的所有成员
     * 
     * @param session 发送消息的 WebSocket 会话
     * @param message 接收到的文本消息
     * @throws Exception 如果处理过程中发生错误
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        try {
            String payload = message.getPayload();
            MessageDTO messageDTO = objectMapper.readValue(payload, MessageDTO.class);
            
            // 设置消息发送时间
            messageDTO.setTimestamp(new Date());
            
            Long userId = (Long) session.getAttributes().get("userId");
            Long groupId = messageDTO.getGroupId();
            
            if (groupId != null) {
                // 设置发送者ID和名称
                messageDTO.setSenderId(userId);
                messageDTO.setSenderName(getUsernameFromUserId(userId));
                
                // 保存消息到数据库
                try {
                    groupService.saveGroupMessage(messageDTO);
                } catch (Exception e) {
                    logger.error("保存消息失败", e);
                }
                
                // 广播消息给群组内所有人
                broadcastToGroup(groupId, messageDTO, null);
                
                logger.info("用户 {} 向群组 {} 发送消息: {}", userId, groupId, messageDTO.getContent());
            }
        } catch (Exception e) {
            logger.error("处理消息时出错", e);
            sendErrorMessage(session, "消息处理失败: " + e.getMessage());
        }
    }
    
    /**
     * 处理 WebSocket 连接关闭事件
     * 
     * 当 WebSocket 连接关闭时调用此方法。
     * 主要职责：
     * 1. 从会话映射中移除用户会话
     * 2. 如果是群聊，从群组会话映射中移除该用户
     * 3. 如果群组没有成员了，移除整个群组
     * 4. 向群组内其他成员广播用户离开的消息
     * 
     * @param session 关闭的 WebSocket 会话
     * @param status 关闭状态，包含关闭原因
     * @throws Exception 如果处理过程中发生错误
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        Long userId = (Long) session.getAttributes().get("userId");
        Long groupId = (Long) session.getAttributes().get("groupId");
        
        if (userId != null) {
            // 移除用户会话
            userSessions.remove(userId);
            
            // 如果是群聊，处理用户离开
            if (groupId != null) {
                // 从群组会话中移除
                Map<Long, WebSocketSession> members = groupSessions.get(groupId);
                if (members != null) {
                    members.remove(userId);
                    
                    // 如果群组没有成员了，移除群组
                    if (members.isEmpty()) {
                        groupSessions.remove(groupId);
                    } else {
                        // 广播用户离开消息
                        String username = getUsernameFromUserId(userId);
                        MessageDTO leaveMessage = new MessageDTO(
                            userId, username, groupId, username + " 离开了群聊"
                        );
                        broadcastToGroup(groupId, leaveMessage, userId);
                    }
                }
                
                logger.info("用户 {} 离开群聊 {}", userId, groupId);
            }
        }
    }
    
    /**
     * 处理 WebSocket 传输错误
     * 
     * 当 WebSocket 传输过程中发生错误时调用此方法。
     * 主要职责：
     * 1. 记录错误日志
     * 2. 关闭出错的 WebSocket 会话
     * 
     * @param session 发生错误的 WebSocket 会话
     * @param exception 发生的异常
     * @throws Exception 如果处理过程中发生错误
     */
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        logger.error("WebSocket传输错误", exception);
        if (session.isOpen()) {
            session.close();
        }
    }
    
    /**
     * 广播消息给群组内的所有成员
     * 
     * 将消息发送给指定群组内的所有成员，可以选择排除特定用户（通常是消息发送者本人）。
     * 
     * @param groupId 群组ID
     * @param message 要广播的消息
     * @param excludeUserId 需要排除的用户ID（可以为null，表示不排除任何人）
     */
    private void broadcastToGroup(Long groupId, MessageDTO message, Long excludeUserId) {
        Map<Long, WebSocketSession> members = groupSessions.get(groupId);
        if (members != null) {
            members.forEach((userId, session) -> {
                // 排除特定用户（例如发送者自己）
                if (excludeUserId == null || !userId.equals(excludeUserId)) {
                    try {
                        if (session.isOpen()) {
                            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(message)));
                        }
                    } catch (IOException e) {
                        logger.error("发送消息给用户 {} 失败", userId, e);
                    }
                }
            });
        }
    }
    
    /**
     * 发送错误消息
     * 
     * 当处理消息过程中发生错误时，向客户端发送包含错误信息的系统消息。
     * 
     * @param session 要发送错误消息的 WebSocket 会话
     * @param errorMessage 错误消息内容
     */
    private void sendErrorMessage(WebSocketSession session, String errorMessage) {
        try {
            MessageDTO error = new MessageDTO();
            error.setContent(errorMessage);
            error.setTimestamp(new Date());
            // 设置系统消息的发送者ID为0，表示系统消息
            error.setSenderId(0L);
            error.setSenderName("系统");
            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(error)));
        } catch (IOException e) {
            logger.error("发送错误消息失败", e);
        }
    }
    
    /**
     * 根据用户ID获取用户名
     * 
     * 此方法用于获取用户的显示名称。在实际应用中，应该从用户服务获取真实的用户名。
     * 当前实现为简化处理，仅返回 "User-" 前缀加用户ID。
     * 
     * @param userId 用户ID
     * @return 用户名
     */
    private String getUsernameFromUserId(Long userId) {
        // 此处简化处理，实际应调用用户服务获取用户名
        return "User-" + userId;
    }
} 